package lumis.util.security.acl.serialization;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

import lumis.portal.PortalException;
import lumis.portal.PortalObjectNotFoundException;
import lumis.portal.authentication.SessionConfig;
import lumis.portal.manager.ManagerFactory;
import lumis.portal.manager.ManagerFactoryInternal;
import lumis.portal.principal.PrincipalConfig;
import lumis.portal.principal.serialization.IPrincipalSerializationManager;
import lumis.portal.serialization.BaseSerializationContext;
import lumis.portal.serialization.DeserializationContext;
import lumis.portal.serialization.ISerializationMessageConfigNodeBuilder;
import lumis.portal.serialization.SerializationContext;
import lumis.portal.serialization.SerializationException;
import lumis.portal.serialization.DeserializationContext.ImportMemberRules.ForCreate;
import lumis.portal.serialization.DeserializationContext.ImportMemberRules.ForUpdate;
import lumis.portal.serialization.operation.ConfigForeignKey;
import lumis.util.security.acl.AccessControlEntry;
import lumis.util.security.acl.AccessControlList;
import lumis.util.security.acl.IAclManager;

/**
 * Manager for Acl serialization
 * 
 * @version $Revision: 12710 $ $Date: 2011-03-18 17:00:16 -0300 (Fri, 18 Mar 2011) $
 * @since 4.0.7
 */
public abstract class AclSerializationManager implements IAclSerializationManager
{
	public class AclGlobalPrincipalEntriesRemovalResult
	{
		private AccessControlList accessControlList = null;
		private List<String> globalPrincipalIds = new ArrayList<String>();
		
		public AccessControlList getAccessControlList()
		{
			return accessControlList;
		}

		public void setAccessControlList(AccessControlList accessControlList)
		{
			this.accessControlList = accessControlList;
		}

		public List<String> getGlobalPrincipalIds()
		{
			return globalPrincipalIds;
		}
	}
	
	protected final String[] primaryKeyFieldNames = new String[] { "id" };

	protected final List<ConfigForeignKey> foreignKeys = Arrays.asList(new ConfigForeignKey[] { 
			new ConfigForeignKey("parentId", null, true, false, true, getAclManager())
	});

	protected final List<ConfigForeignKey> entryForeignKeys = Arrays.asList(new ConfigForeignKey[] { 
			new ConfigForeignKey("principalId", null, true, false, true, ManagerFactory.getPrincipalManager())
	});

	protected final String configAlias = "accessControlList";

	// DESERIALIZATION

	public void deserialize(DeserializationContext deserializationContext, AccessControlListSerializableConfig accessControlListSerializableConfig) throws PortalException
	{
		deserializationContext.logDebug("Deserializing ACL '" + accessControlListSerializableConfig.getId() + "'");

		// deserialize acl
		AccessControlList accessControlList = ManagerFactoryInternal.getSerializationManager2().deserializeObject(AccessControlList.class, deserializationContext.getDirectory(), getConfigPath(deserializationContext, accessControlListSerializableConfig.getId()), configAlias, false);

		// apply id mappings
		deserializationContext.applyPrimaryKeyFieldIdMappings(accessControlList, primaryKeyFieldNames);

		setEntriesBasedOnDeserializationRules(deserializationContext, accessControlList, accessControlListSerializableConfig);
		
		// analyze dependencies
		AclDependencyAnalyzer dependencyAnalyzer = new AclDependencyAnalyzer(deserializationContext, foreignKeys, entryForeignKeys, accessControlList);
		dependencyAnalyzer.analyze(this);

		// persist config
		if (dependencyAnalyzer.getCanPersist())
			persistConfig(deserializationContext, dependencyAnalyzer.getPersistableConfig(), true);
		else
			throw new IllegalStateException("Error persisting deserialized ACL.");
	}

	public AccessControlList deserializeConfig(DeserializationContext deserializationContext, String accessControlListId) throws SerializationException
	{
		deserializationContext.logDebug("Deserializing ACL object");
		return ManagerFactoryInternal.getSerializationManager2().deserializeObject(AccessControlList.class, deserializationContext.getDirectory(), getConfigPath(deserializationContext, accessControlListId), configAlias, false);
	}

	public boolean persistConfig(DeserializationContext deserializationContext, Object config, boolean firstPersistence) throws PortalException
	{
		boolean persisted = false;

		AccessControlList accessControlList = (AccessControlList) config;

		// apply foreign key id mappings and analyze dependencies
		AclDependencyAnalyzer dependencyAnalyzer = new AclDependencyAnalyzer(deserializationContext, foreignKeys, entryForeignKeys, accessControlList);
		if (dependencyAnalyzer.foreignKeysExists())
		{
			// add or update acl
			if (deserializationContext.isUpdateIfElementExists() || !firstPersistence)
			{
				deserializationContext.logDebug("Adding/updating ACL:", dependencyAnalyzer.getMappedPersistableConfig());
				getAclManager().addOrUpdate(dependencyAnalyzer.getMappedPersistableConfig(), deserializationContext.getTransaction());
			}
			else
			{
				deserializationContext.logDebug("Adding ACL:", dependencyAnalyzer.getMappedPersistableConfig());
				getAclManager().add(dependencyAnalyzer.getMappedPersistableConfig(), deserializationContext.getTransaction());
			}
			persisted = true;
		}
		return persisted;
	}

	protected abstract IAclManager getAclManager();

	// SERIALIZATION

	public abstract AccessControlListSerializableConfig serialize(SerializationContext serializationContext, String securableId) throws PortalException;

	protected AccessControlListSerializableConfig createSerializableConfig(SerializationContext serializationContext, AccessControlList accessControlList, AccessControlListSerializableConfig.AclType aclType, String objectId) throws PortalException
	{
		AccessControlListSerializableConfig accessControlListSerializableConfig = new AccessControlListSerializableConfig();
		accessControlListSerializableConfig.setId(accessControlList.getId());
		accessControlListSerializableConfig.setType(aclType);
		accessControlListSerializableConfig.setAclForObjectId(objectId);
		
		List<AccessControlListEntrySerializableConfig> serializableEntries = accessControlListSerializableConfig.getEntries();
		// set the serializable entries here
		for (AccessControlEntry aclEntry : accessControlList.getLocalEntries())
		{
			AccessControlListEntrySerializableConfig aclEntrySerializableConfig = serializeEntry(serializationContext, aclEntry);
			serializableEntries.add(aclEntrySerializableConfig);
		}
		
		return accessControlListSerializableConfig;
	}

	private AccessControlListEntrySerializableConfig serializeEntry(final SerializationContext serializationContext, final AccessControlEntry aclEntry) throws PortalException
	{
		// build structure object
		AccessControlListEntrySerializableConfig accessControlListEntrySerializableConfig = getAclEntrySerializableConfig(serializationContext, aclEntry.getPrincipalId());
		
		// return structure object
		return accessControlListEntrySerializableConfig;
	}

	private AccessControlListEntrySerializableConfig getAclEntrySerializableConfig(SerializationContext serializationContext, String groupMemberId) throws PortalException
	{
		AccessControlListEntrySerializableConfig aclEntrySerializableConfig = new AccessControlListEntrySerializableConfig();
		aclEntrySerializableConfig.setId(groupMemberId);

		PrincipalConfig principalConfig = ManagerFactory.getPrincipalManager().get(serializationContext.getSessionConfig(), groupMemberId, serializationContext.getTransaction());
		aclEntrySerializableConfig.setPrincipalType(principalConfig.getType());
		aclEntrySerializableConfig.setChannelId(principalConfig.getChannelId());
		
		aclEntrySerializableConfig.setExternal(!serializationContext.getChannelIds().contains(principalConfig.getChannelId()) && !serializationContext.getGlobalGroups().contains(groupMemberId));
		
		return aclEntrySerializableConfig;
	}
	
	protected void analyzeExternalDependencies(SerializationContext serializationContext, ISerializationMessageConfigNodeBuilder serializationMessageConfigNodeBuilder, AccessControlList dependentAccessControlList) throws PortalException
	{
		final IPrincipalSerializationManager principalSerializationManager = ManagerFactoryInternal.getPrincipalSerializationManager();
		for (AccessControlEntry entry : dependentAccessControlList.getEntries())
			principalSerializationManager.analyzeExternalDependencies(serializationContext, serializationMessageConfigNodeBuilder, entry.getPrincipalId());
	}

	// GENERAL

	public void setupSerializationContext(BaseSerializationContext serializationContext)
	{
		// define serialization aliases
		serializationContext.getSerializationSettings().getClassAliases().put(AccessControlListSerializableConfig.class, "accessControlList");
		serializationContext.getSerializationSettings().getTransientFieldsForClass(AccessControlList.class).add("parentACL");
	}

	/**
	 * Get the full path for the serialization file.
	 * @param deserializationContext
	 * @param configId
	 * @return
	 */
	protected String getConfigPath(BaseSerializationContext deserializationContext, String configId)
	{
		return "accessControlLists/" + configId + ".xml";
	}
	
	
	/**
	 * Based on the deserialization rules, acl entries are included or removed.
	 * 
	 * @param deserializationContext
	 * @param accessControlList
	 * @param accessControlListSerializableConfig
	 * @throws PortalException
	 * @since 4.2.0
	 */
	private void setEntriesBasedOnDeserializationRules(DeserializationContext deserializationContext, AccessControlList accessControlList, AccessControlListSerializableConfig accessControlListSerializableConfig) throws PortalException
	{
		ForCreate forCreate = deserializationContext.getImportMemberRules().getForCreate();
		ForUpdate forUpdate = deserializationContext.getImportMemberRules().getForUpdate();

		AccessControlList currentAccessControlList = null;
		
		try
		{
			switch(accessControlListSerializableConfig.getType())
			{
				case CHANNEL:
					currentAccessControlList = ManagerFactory.getChannelAclManager().get(SessionConfig.getCurrentSessionConfig(), deserializationContext.getIdMapping(accessControlListSerializableConfig.getAclForObjectId()), deserializationContext.getTransaction());
					break;
				case PAGE:
					currentAccessControlList = ManagerFactory.getPageAclManager().get(SessionConfig.getCurrentSessionConfig(), deserializationContext.getIdMapping(accessControlListSerializableConfig.getAclForObjectId()), deserializationContext.getTransaction());
					break;
				case GROUP:
					currentAccessControlList = ManagerFactory.getGroupAclManager().get(SessionConfig.getCurrentSessionConfig(), deserializationContext.getIdMapping(accessControlListSerializableConfig.getAclForObjectId()), deserializationContext.getTransaction());
					break;
				case SERVICE_INSTANCE:
					currentAccessControlList = ManagerFactory.getServiceInstanceAclManager().get(SessionConfig.getCurrentSessionConfig(), deserializationContext.getIdMapping(accessControlListSerializableConfig.getAclForObjectId()), deserializationContext.getTransaction());
					break;
			}
		}
		catch(PortalObjectNotFoundException e)
		{
			// new object
		}
		
		HashMap<String, AccessControlEntry> entries = new HashMap<String, AccessControlEntry>();
		
		boolean updating = currentAccessControlList != null;
		
		if(updating)
		{
			// updating
			AccessControlEntry[] currentEntries = currentAccessControlList.getLocalEntries();
			for (int i = 0; i < currentEntries.length; i++)
			{
				boolean deleteMember = false;
			
				// existing ACL
				if(forUpdate.equals(ForUpdate.ALL_MEMBERS))
				{
					deleteMember = true;
				}
				else
				{
					PrincipalConfig principalConfig = ManagerFactory.getPrincipalManager().get(deserializationContext.getSessionConfig(), currentEntries[i].getPrincipalId(), deserializationContext.getTransaction());

					if(principalConfig.getType() == PrincipalConfig.TYPE_GROUP)
					{
						if(forUpdate.equals(ForUpdate.ALL_MEMBERS_OF_TYPE_GROUP) || forUpdate.equals(ForUpdate.ALL_MEMBERS_OF_TYPE_GROUP_IGNORING_NON_EXISTENT_GROUPS))
						{
							deleteMember = true;
						}
						else if(principalConfig.getChannelId() != null)
						{
							// delete only local members
							// make sure the channel id of the member is within the root channel structure.
							String rootChannelMappedId = deserializationContext.getIdMapping(deserializationContext.getRootChannelId());
							String principalChannelMappedId = deserializationContext.getIdMapping(principalConfig.getChannelId());
							if(rootChannelMappedId.equals(principalChannelMappedId) ||
									 ManagerFactory.getChannelManager().getPathIds(deserializationContext.getSessionConfig(), principalChannelMappedId, deserializationContext.getTransaction()).contains(rootChannelMappedId))
								 	deleteMember = true;
						}
					}
				}
				
				if(!deleteMember)
					entries.put(currentEntries[i].getPrincipalId(), currentEntries[i]);
			}
		}
		
		// now add all the appropriate members from the imported ACL
		for(AccessControlListEntrySerializableConfig aclEntry : accessControlListSerializableConfig.getEntries())
		{
			boolean addMember = false;
			
			if( (updating && forUpdate.equals(ForUpdate.ALL_MEMBERS)) ||
					(!updating && forCreate.equals(ForCreate.ALL_MEMBERS)))
			{
				addMember = true;
			}
			else
			{
				if(aclEntry.getPrincipalType() == PrincipalConfig.TYPE_GROUP)
				{
					if( (updating && forUpdate.equals(DeserializationContext.ImportMemberRules.ForUpdate.ALL_MEMBERS_OF_TYPE_GROUP)) ||
						(!updating && forCreate.equals(DeserializationContext.ImportMemberRules.ForCreate.ALL_MEMBERS_OF_TYPE_GROUP)))
					{
						addMember = true;
					}
					else if( (updating && forUpdate.equals(DeserializationContext.ImportMemberRules.ForUpdate.ALL_MEMBERS_OF_TYPE_GROUP_IGNORING_NON_EXISTENT_GROUPS)) ||
							(!updating && forCreate.equals(DeserializationContext.ImportMemberRules.ForCreate.ALL_MEMBERS_OF_TYPE_GROUP_IGNORING_NON_EXISTENT_GROUPS)))
					{
						if(aclEntry.isExternal())
						{
							// check if member exists
							try
							{
								addMember = true;
								ManagerFactory.getGroupManager().get(deserializationContext.getSessionConfig(), aclEntry.getId(), deserializationContext.getTransaction());
							}
							catch(PortalObjectNotFoundException e)
							{
								addMember = false;
							}
						}
						else
						{
							addMember =true;
						}
					}
					else if(aclEntry.getChannelId() != null)
					{
						// adds only local members
						// make sure the channel id of the member is within the root channel structure.
						 String rootChannelMappedId = deserializationContext.getIdMapping(deserializationContext.getRootChannelId());
						String principalChannelMappedId = deserializationContext.getIdMapping(aclEntry.getChannelId());
						if(rootChannelMappedId.equals(principalChannelMappedId) ||
								 ManagerFactory.getChannelManager().getPathIds(deserializationContext.getSessionConfig(), principalChannelMappedId, deserializationContext.getTransaction()).contains(rootChannelMappedId))
							addMember = true;
					}
				}
			}
			
			if(addMember)
			{
				entries.put(aclEntry.getId(), accessControlList.getEntry(aclEntry.getId()));
			}
		}
		
		AccessControlEntry[] getEntries = new AccessControlEntry[entries.size()];
		accessControlList.setLocalEntries(entries.values().toArray(getEntries));
	}
}
